import re
import threading
from typing import List


from domain.note.noteDomain import NoteDomain
from domain.util.pageDomain import PageDomain, PageResult
from util.dbHelper import MySQLHelper


def SQLWrapper(sql, sqlType, returnType):
    def decorate(func):
        setattr(func, 'sqlType', sqlType)
        setattr(func, 'sql', sql)
        setattr(func, 'returnType', returnType)
        return func
    return decorate


def Select(sql, returnType):
    return SQLWrapper(sql, Select, returnType)


def Insert(sql):
    return SQLWrapper(sql, Insert, int)


def Update(sql):
    return SQLWrapper(sql, Update, int)


def Delete(sql):
    return SQLWrapper(sql, Delete, int)


class OrmProxy:
    LOCAL_PAGE = threading.local()
    FROM_RE = re.compile('from (.*)', re.M | re.I | re.S)
    FIRST_WORD_RE = re.compile('\w*')

    def __init__(self, obj, method):
        self.obj = obj
        self.method = method
        self.pageSize = None
        self.pageNum = None
        self.orderBy = None
        self.sql = getattr(self.method, 'sql')
        self.sqlType = getattr(self.method, 'sqlType')
        self.returnType = getattr(self.method, 'returnType')
        self.argsGenerator = None
        self.sqlGenerator = None
        self.preparedSql = None
        self.replaceSqlArgs()

    @staticmethod
    def underscoreToCamelCase(s: str):
        words = s.split('_')
        res = words[0]
        for word in words[1:]:
            res += word.title()
        return res

    @staticmethod
    def camelCaseToUnderscore(s: str):
        res = ''
        for i in s:
            res += '_' + i.lower() if i.isupper() else i
        return res

    @classmethod
    def getCamelCaseMap(cls, returnType, mapUnderscoreToCamelCase=True):
        res = {}
        for key in returnType().__dict__:
            underscore = cls.camelCaseToUnderscore(key)
            res[underscore] = key if mapUnderscoreToCamelCase else underscore
        return res

    @staticmethod
    def getEntityFromDict(returnType, row: dict, keyMap: dict):
        res = returnType()
        for key, value in row.items():
            key = keyMap[key]
            if key:
                setattr(res, key, value)
        return res

    @classmethod
    def getDynamicParam(cls, expression, kwargs):
        key = cls.FIRST_WORD_RE.match(expression).group()
        value = eval("kwargs['{}']{}".format(key, expression[len(key):]))
        return value

    @classmethod
    def judgeDynamicExpression(cls, expression: str, kwargs):
        for key, value in kwargs.items():
            varExpr = re.findall('({}(?:\.\w+)?)'.format(key), expression)
            if varExpr:
                for expr in varExpr:
                    key = cls.FIRST_WORD_RE.match(expression).group()
                    expression = expression.replace(expr, "kwargs['{}']{}".format(key, expr[len(key):]))
        try:
            return eval(expression)
        except NameError as e:
            return False

    def replaceSqlArgs(self):
        ifLabels = re.findall('<if test="(.*?)">(.*?)</if>', self.sql, re.M)
        if ifLabels:
            sqlPart = re.split('<if test=".*?">.*?</if>', self.sql, re.M)

            def sqlGenerator(kwargs) -> str:
                sql = sqlPart[0]
                for i in range(len(ifLabels)):
                    judgeExpr, dynamicPart = ifLabels[i]
                    part0 = sqlPart[i + 1]
                    if self.judgeDynamicExpression(judgeExpr, kwargs):
                        sql += dynamicPart
                    sql += part0
                return sql

            def argsGenerator(sql, kwargs) -> list:
                dph = re.findall('#{(.*?)}', sql, re.M)
                dsp = re.split('#{.*?}', sql, re.M)
                res = []
                self.preparedSql = dsp[0]
                for part1 in dsp[1:]:
                    self.preparedSql += '%s' + part1
                for ph in dph:
                    res.append(self.getDynamicParam(ph, kwargs))
                return res
        else:
            placeholders = re.findall('#{(.*?)}', self.sql, re.M)
            sqlPart = re.split('#{.*?}', self.sql, re.M)
            self.preparedSql = sqlPart[0]
            for part in sqlPart[1:]:
                self.preparedSql += '%s' + part

            def sqlGenerator(kwargs) -> str:
                return self.preparedSql

            def argsGenerator(_, kwargs) -> list:
                res = []
                for ph in placeholders:
                    res.append(self.getDynamicParam(ph, kwargs))
                return res
        self.sqlGenerator = sqlGenerator
        self.argsGenerator = argsGenerator

    def _query(self, conn: MySQLHelper, sql: str, returnType, args):
        total = None
        page = getattr(self.LOCAL_PAGE, 'page') if hasattr(self.LOCAL_PAGE, 'page') else None
        if page:
            delattr(self.LOCAL_PAGE, 'page')
            fromMatch = self.FROM_RE.search(sql)
            countSql = 'select count(*) as cnt ' + fromMatch.group() if fromMatch else ''
            total = conn.select_one(countSql, args)[1]['cnt']
            if page.orderBy:
                args.append(page.orderBy)
                sql += ' order by %s '
            sql += ' limit %s, %s'
            args.append(page.getStart())
            args.append(page.pageSize)
        sqlResult = conn.select_many(sql, args)
        if isinstance(returnType, type(List)) and getattr(returnType, '_name') == 'List':
            t = getattr(returnType, '__args__')[0]
            res = PageResult(total) if total else []
            keyMap = self.getCamelCaseMap(t)
            for sqlRow in sqlResult[1]:
                res.append(OrmProxy.getEntityFromDict(t, sqlRow, keyMap))
        else:
            keyMap = self.getCamelCaseMap(returnType)
            res = OrmProxy.getEntityFromDict(returnType, sqlResult, keyMap)
        return res

    def __call__(self, **kwargs):
        conn = MySQLHelper()
        ret = None
        try:
            sql = self.sqlGenerator(kwargs)
            args = self.argsGenerator(sql, kwargs)
            if self.sqlType == Select:
                ret = self._query(conn, self.preparedSql, self.returnType, args)
            elif self.sqlType == Insert:
                ret = conn.insert_one(self.preparedSql, args)
            elif self.sqlType == Update:
                ret = conn.update(self.preparedSql, args)
            elif self.sqlType == Delete:
                ret = conn.delete(self.preparedSql, args)
            conn.commit()
        except Exception as e:
            print(e)
            conn.commit('rollback')
        finally:
            conn.close()
        return ret

    @classmethod
    def startPage(cls, pageNum, pageSize, orderBy=None):
        cls.LOCAL_PAGE.page = PageDomain(pageNum, pageSize, orderBy)

    @staticmethod
    def getTotal(result: list):
        if isinstance(result, PageResult):
            return result.total
        return len(result) if result else 0


if __name__ == '__main__':
    print(OrmProxy.getCamelCaseMap(NoteDomain))

